import {
    BoxGeometry, BoxBufferGeometry,
    BufferGeometry,
    CircleGeometry, CircleBufferGeometry,
    Color,
    ConeGeometry, ConeBufferGeometry,
    Curve,
    CylinderGeometry, CylinderBufferGeometry,
    DodecahedronGeometry, DodecahedronBufferGeometry,
    DoubleSide,
    ExtrudeGeometry, ExtrudeBufferGeometry,
    Float32BufferAttribute,
    FontLoader,
    Group,
    IcosahedronGeometry, IcosahedronBufferGeometry,
    LatheGeometry, LatheBufferGeometry,
    LineSegments,
    LineBasicMaterial,
    Mesh,
    MeshPhongMaterial, PointsMaterial, Points,
    OctahedronGeometry, OctahedronBufferGeometry,
    ParametricGeometry, ParametricBufferGeometry,
    PerspectiveCamera,
    PlaneGeometry, PlaneBufferGeometry,
    PointLight,
    RingGeometry, RingBufferGeometry,
    Scene,
    Shape,
    ShapeGeometry, ShapeBufferGeometry,
    SphereGeometry, SphereBufferGeometry,
    TetrahedronGeometry, TetrahedronBufferGeometry,
    TextGeometry, TextBufferGeometry,
    TorusGeometry, TorusBufferGeometry,
    TorusKnotGeometry, TorusKnotBufferGeometry,
    TubeGeometry, TubeBufferGeometry,
    Vector2,
    Vector3,
    WireframeGeometry, EdgesGeometry,
    WebGLRenderer
} from "three";

import {GUI} from 'three/examples/jsm/libs/dat.gui.module.js';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
import {ParametricGeometries} from 'three/examples/jsm/geometries/ParametricGeometries.js';

import '../../../css/base.css'
import {baseUrl} from '../../../js/base.js'

const twoPi = Math.PI * 2;
let isShowGeometry = ''

class CustomSinCurve extends Curve {

    constructor(scale = 1) {

        super();

        this.scale = scale;

    }

    getPoint(t, optionalTarget = new Vector3()) {

        const tx = t * 3 - 1.5;
        const ty = Math.sin(2 * Math.PI * t);
        const tz = 0;

        return optionalTarget.set(tx, ty, tz).multiplyScalar(this.scale);

    }

}

function updateGroupGeometry(mesh, geometry) {

    if (geometry.isGeometry) {

        geometry = new BufferGeometry().fromGeometry(geometry);

        console.warn('THREE.GeometryBrowser: Converted Geometry to BufferGeometry.');
    }

    mesh.children[0].geometry.dispose();
    mesh.children[1].geometry.dispose();

    if (isShowGeometry === 'EdgesGeometry') {
        mesh.children[0].geometry = new EdgesGeometry(geometry);
    } else {
        mesh.children[0].geometry = new WireframeGeometry(geometry);
    }
    mesh.children[1].geometry = geometry;

    if (isShowGeometry === 'EdgesGeometry' || isShowGeometry === 'WireframeGeometry') {
        mesh.children[1].visible = false
    } else {
        mesh.children[1].visible = true
    }
    // these do not update nicely together if shared

    isShowGeometry = ''
}

// heart shape

const x = 0, y = 0;

const heartShape = new Shape();

heartShape.moveTo(x + 5, y + 5);
heartShape.bezierCurveTo(x + 5, y + 5, x + 4, y, x, y);
heartShape.bezierCurveTo(x - 6, y, x - 6, y + 7, x - 6, y + 7);
heartShape.bezierCurveTo(x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19);
heartShape.bezierCurveTo(x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7);
heartShape.bezierCurveTo(x + 16, y + 7, x + 16, y, x + 10, y);
heartShape.bezierCurveTo(x + 7, y, x + 5, y + 5, x + 5, y + 5);

const guis = {

    BoxBufferGeometry: function (mesh) {

        const data = {
            width: 15,
            height: 15,
            depth: 15,
            widthSegments: 1,
            heightSegments: 1,
            depthSegments: 1
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new BoxBufferGeometry(
                    data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments
                )
            );

        }

        const folder = gui.addFolder('THREE.BoxBufferGeometry');

        folder.add(data, 'width', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 30).onChange(generateGeometry);
        folder.add(data, 'depth', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'depthSegments', 1, 10).step(1).onChange(generateGeometry);

        generateGeometry();
        this['BoxBufferGeometry'].generateGeometry = generateGeometry


    },

    BoxGeometry: function (mesh) {

        const data = {
            width: 15,
            height: 15,
            depth: 15,
            widthSegments: 1,
            heightSegments: 1,
            depthSegments: 1
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new BoxGeometry(
                    data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments
                )
            );

        }

        const folder = gui.addFolder('THREE.BoxGeometry');

        folder.add(data, 'width', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 30).onChange(generateGeometry);
        folder.add(data, 'depth', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'depthSegments', 1, 10).step(1).onChange(generateGeometry);

        generateGeometry();
        this['BoxGeometry'].generateGeometry = generateGeometry


    },

    CylinderBufferGeometry: function (mesh) {

        const data = {
            radiusTop: 5,
            radiusBottom: 5,
            height: 10,
            radialSegments: 8,
            heightSegments: 1,
            openEnded: false,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new CylinderBufferGeometry(
                    data.radiusTop,
                    data.radiusBottom,
                    data.height,
                    data.radialSegments,
                    data.heightSegments,
                    data.openEnded,
                    data.thetaStart,
                    data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.CylinderBufferGeometry');

        folder.add(data, 'radiusTop', 0, 30).onChange(generateGeometry);
        folder.add(data, 'radiusBottom', 0, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 50).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'openEnded').onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);


        generateGeometry();
        this['CylinderBufferGeometry'].generateGeometry = generateGeometry


    },

    CylinderGeometry: function (mesh) {

        const data = {
            radiusTop: 5,
            radiusBottom: 5,
            height: 10,
            radialSegments: 8,
            heightSegments: 1,
            openEnded: false,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new CylinderGeometry(
                    data.radiusTop,
                    data.radiusBottom,
                    data.height,
                    data.radialSegments,
                    data.heightSegments,
                    data.openEnded,
                    data.thetaStart,
                    data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.CylinderGeometry');

        folder.add(data, 'radiusTop', 1, 30).onChange(generateGeometry);
        folder.add(data, 'radiusBottom', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 50).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'openEnded').onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);


        generateGeometry();
        this['CylinderGeometry'].generateGeometry = generateGeometry


    },

    ConeBufferGeometry: function (mesh) {

        const data = {
            radius: 5,
            height: 10,
            radialSegments: 8,
            heightSegments: 1,
            openEnded: false,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new ConeBufferGeometry(
                    data.radius,
                    data.height,
                    data.radialSegments,
                    data.heightSegments,
                    data.openEnded,
                    data.thetaStart,
                    data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.ConeBufferGeometry');

        folder.add(data, 'radius', 0, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 50).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'openEnded').onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);


        generateGeometry();
        this['ConeBufferGeometry'].generateGeometry = generateGeometry


    },

    ConeGeometry: function (mesh) {

        const data = {
            radius: 5,
            height: 10,
            radialSegments: 8,
            heightSegments: 1,
            openEnded: false,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new ConeGeometry(
                    data.radius,
                    data.height,
                    data.radialSegments,
                    data.heightSegments,
                    data.openEnded,
                    data.thetaStart,
                    data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.ConeGeometry');

        folder.add(data, 'radius', 0, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 50).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 64).step(1).onChange(generateGeometry);
        folder.add(data, 'openEnded').onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);


        generateGeometry();
        this['ConeGeometry'].generateGeometry = generateGeometry


    },

    CircleBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            segments: 32,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new CircleBufferGeometry(
                    data.radius, data.segments, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.CircleBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'segments', 0, 128).step(1).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['CircleBufferGeometry'].generateGeometry = generateGeometry


    },

    CircleGeometry: function (mesh) {

        const data = {
            radius: 10,
            segments: 32,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new CircleGeometry(
                    data.radius, data.segments, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.CircleGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'segments', 0, 128).step(1).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['CircleGeometry'].generateGeometry = generateGeometry


    },

    DodecahedronGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new DodecahedronGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.DodecahedronGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['DodecahedronGeometry'].generateGeometry = generateGeometry


    },

    DodecahedronBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new DodecahedronBufferGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.DodecahedronBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['DodecahedronBufferGeometry'].generateGeometry = generateGeometry


    },

    EdgesGeometry: function (mesh) {
        isShowGeometry = 'EdgesGeometry'
        const selects = []
        Object.keys(guis).forEach(value => selects.push(value))

        const data = {
            select: 'BoxBufferGeometry',
        };

        function generateGeometry() {
            const geometry = new BoxGeometry(data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments)
            updateGroupGeometry(mesh, geometry);
        }

        const folder = gui.addFolder('THREE.EdgesGeometry');

        folder.add(data, 'select', selects).onChange((value) => {
            console.log(value)
            isShowGeometry = 'EdgesGeometry'
            this[data.select].generateGeometry()
        });

        this[data.select].generateGeometry()
        this['EdgesGeometry'].generateGeometry = generateGeometry
    },

    IcosahedronGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new IcosahedronGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.IcosahedronGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['IcosahedronGeometry'].generateGeometry = generateGeometry


    },

    IcosahedronBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new IcosahedronBufferGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.IcosahedronBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['IcosahedronBufferGeometry'].generateGeometry = generateGeometry


    },

    LatheBufferGeometry: function (mesh) {

        const points = [];

        for (let i = 0; i < 10; i++) {

            points.push(new Vector2(Math.sin(i * 0.2) * 10 + 5, (i - 5) * 2));

        }

        const data = {
            segments: 12,
            phiStart: 0,
            phiLength: twoPi
        };

        function generateGeometry() {

            const geometry = new LatheBufferGeometry(
                points, data.segments, data.phiStart, data.phiLength
            );

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.LatheBufferGeometry');

        folder.add(data, 'segments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'phiStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'phiLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['LatheBufferGeometry'].generateGeometry = generateGeometry


    },

    LatheGeometry: function (mesh) {

        const points = [];

        for (let i = 0; i < 10; i++) {

            points.push(new Vector2(Math.sin(i * 0.2) * 10 + 5, (i - 5) * 2));

        }

        const data = {
            segments: 12,
            phiStart: 0,
            phiLength: twoPi
        };

        function generateGeometry() {

            const geometry = new LatheGeometry(
                points, data.segments, data.phiStart, data.phiLength
            );

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.LatheGeometry');

        folder.add(data, 'segments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'phiStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'phiLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['LatheGeometry'].generateGeometry = generateGeometry


    },

    OctahedronGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new OctahedronGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.OctahedronGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['OctahedronGeometry'].generateGeometry = generateGeometry


    },

    OctahedronBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new OctahedronBufferGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.OctahedronBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['OctahedronBufferGeometry'].generateGeometry = generateGeometry


    },

    PlaneBufferGeometry: function (mesh) {

        const data = {
            width: 10,
            height: 10,
            widthSegments: 1,
            heightSegments: 1
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new PlaneBufferGeometry(
                    data.width, data.height, data.widthSegments, data.heightSegments
                )
            );

        }

        const folder = gui.addFolder('THREE.PlaneBufferGeometry');

        folder.add(data, 'width', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 30).step(1).onChange(generateGeometry);

        generateGeometry();
        this['PlaneBufferGeometry'].generateGeometry = generateGeometry


    },

    PlaneGeometry: function (mesh) {

        const data = {
            width: 10,
            height: 10,
            widthSegments: 1,
            heightSegments: 1
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new PlaneGeometry(
                    data.width, data.height, data.widthSegments, data.heightSegments
                )
            );

        }

        const folder = gui.addFolder('THREE.PlaneGeometry');

        folder.add(data, 'width', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 1, 30).step(1).onChange(generateGeometry);

        generateGeometry();
        this['PlaneGeometry'].generateGeometry = generateGeometry


    },

    RingBufferGeometry: function (mesh) {

        const data = {
            innerRadius: 5,
            outerRadius: 10,
            thetaSegments: 8,
            phiSegments: 8,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new RingBufferGeometry(
                    data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.RingBufferGeometry');

        folder.add(data, 'innerRadius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'outerRadius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'thetaSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'phiSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['RingBufferGeometry'].generateGeometry = generateGeometry


    },

    RingGeometry: function (mesh) {

        const data = {
            innerRadius: 5,
            outerRadius: 10,
            thetaSegments: 8,
            phiSegments: 8,
            thetaStart: 0,
            thetaLength: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new RingGeometry(
                    data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.RingGeometry');

        folder.add(data, 'innerRadius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'outerRadius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'thetaSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'phiSegments', 1, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['RingGeometry'].generateGeometry = generateGeometry


    },

    SphereBufferGeometry: function (mesh) {

        const data = {
            radius: 15,
            widthSegments: 8,
            heightSegments: 6,
            phiStart: 0,
            phiLength: twoPi,
            thetaStart: 0,
            thetaLength: Math.PI
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new SphereBufferGeometry(
                    data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.SphereBufferGeometry');

        folder.add(data, 'radius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 3, 32).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 2, 32).step(1).onChange(generateGeometry);
        folder.add(data, 'phiStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'phiLength', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['SphereBufferGeometry'].generateGeometry = generateGeometry


    },

    SphereGeometry: function (mesh) {

        const data = {
            radius: 15,
            widthSegments: 8,
            heightSegments: 6,
            phiStart: 0,
            phiLength: twoPi,
            thetaStart: 0,
            thetaLength: Math.PI
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new SphereGeometry(
                    data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength
                )
            );

        }

        const folder = gui.addFolder('THREE.SphereGeometry');

        folder.add(data, 'radius', 1, 30).onChange(generateGeometry);
        folder.add(data, 'widthSegments', 3, 32).step(1).onChange(generateGeometry);
        folder.add(data, 'heightSegments', 2, 32).step(1).onChange(generateGeometry);
        folder.add(data, 'phiStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'phiLength', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaStart', 0, twoPi).onChange(generateGeometry);
        folder.add(data, 'thetaLength', 0, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['SphereGeometry'].generateGeometry = generateGeometry


    },

    TetrahedronGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TetrahedronGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.TetrahedronGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TetrahedronGeometry'].generateGeometry = generateGeometry


    },

    TetrahedronBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            detail: 0
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TetrahedronBufferGeometry(
                    data.radius, data.detail
                )
            );

        }

        const folder = gui.addFolder('THREE.TetrahedronBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'detail', 0, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TetrahedronBufferGeometry'].generateGeometry = generateGeometry


    },

    TextGeometry: function (mesh) {

        const data = {
            text: "TextGeometry",
            size: 5,
            height: 2,
            curveSegments: 12,
            font: "helvetiker",
            weight: "regular",
            bevelEnabled: false,
            bevelThickness: 1,
            bevelSize: 0.5,
            bevelOffset: 0.0,
            bevelSegments: 3
        };

        const fonts = [
            "helvetiker",
            "optimer",
            "gentilis",
            "droid/droid_serif"
        ];

        const weights = [
            "regular", "bold"
        ];

        function generateGeometry() {

            const loader = new FontLoader();
            loader.load(`${baseUrl}static/examples/fonts/` + data.font + '_' + data.weight + '.typeface.json', function (font) {

                const geometry = new TextGeometry(data.text, {
                    font: font,
                    size: data.size,
                    height: data.height,
                    curveSegments: data.curveSegments,
                    bevelEnabled: data.bevelEnabled,
                    bevelThickness: data.bevelThickness,
                    bevelSize: data.bevelSize,
                    bevelOffset: data.bevelOffset,
                    bevelSegments: data.bevelSegments
                });
                geometry.center();

                updateGroupGeometry(mesh, geometry);

            });

        }

        //Hide the wireframe
        mesh.children[0].visible = true;

        const folder = gui.addFolder('THREE.TextGeometry');

        folder.add(data, 'text').onChange(generateGeometry);
        folder.add(data, 'size', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 20).onChange(generateGeometry);
        folder.add(data, 'curveSegments', 1, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'font', fonts).onChange(generateGeometry);
        folder.add(data, 'weight', weights).onChange(generateGeometry);
        folder.add(data, 'bevelEnabled').onChange(generateGeometry);
        folder.add(data, 'bevelThickness', 0.1, 3).onChange(generateGeometry);
        folder.add(data, 'bevelSize', 0, 3).onChange(generateGeometry);
        folder.add(data, 'bevelOffset', -0.5, 1.5).onChange(generateGeometry);
        folder.add(data, 'bevelSegments', 0, 8).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TextGeometry'].generateGeometry = generateGeometry


    },

    TextBufferGeometry: function (mesh) {

        const data = {
            text: "TextBufferGeometry",
            size: 5,
            height: 2,
            curveSegments: 12,
            font: "helvetiker",
            weight: "regular",
            bevelEnabled: false,
            bevelThickness: 1,
            bevelSize: 0.5,
            bevelOffset: 0.0,
            bevelSegments: 3
        };

        const fonts = [
            "helvetiker",
            "optimer",
            "gentilis",
            "droid/droid_serif"
        ];

        const weights = [
            "regular", "bold"
        ];

        function generateGeometry() {

            const loader = new FontLoader();
            loader.load('static/examples/fonts/' + data.font + '_' + data.weight + '.typeface.json', function (font) {

                const geometry = new TextBufferGeometry(data.text, {
                    font: font,
                    size: data.size,
                    height: data.height,
                    curveSegments: data.curveSegments,
                    bevelEnabled: data.bevelEnabled,
                    bevelThickness: data.bevelThickness,
                    bevelSize: data.bevelSize,
                    bevelOffset: data.bevelOffset,
                    bevelSegments: data.bevelSegments
                });
                geometry.center();

                updateGroupGeometry(mesh, geometry);

            });

        }

        //Hide the wireframe
        mesh.children[0].visible = true;

        const folder = gui.addFolder('THREE.TextBufferGeometry');

        folder.add(data, 'text').onChange(generateGeometry);
        folder.add(data, 'size', 1, 30).onChange(generateGeometry);
        folder.add(data, 'height', 1, 20).onChange(generateGeometry);
        folder.add(data, 'curveSegments', 1, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'font', fonts).onChange(generateGeometry);
        folder.add(data, 'weight', weights).onChange(generateGeometry);
        folder.add(data, 'bevelEnabled').onChange(generateGeometry);
        folder.add(data, 'bevelThickness', 0.1, 3).onChange(generateGeometry);
        folder.add(data, 'bevelSize', 0, 3).onChange(generateGeometry);
        folder.add(data, 'bevelOffset', -0.5, 1.5).onChange(generateGeometry);
        folder.add(data, 'bevelSegments', 0, 8).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TextBufferGeometry'].generateGeometry = generateGeometry


    },

    TorusBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            tube: 3,
            radialSegments: 16,
            tubularSegments: 100,
            arc: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TorusBufferGeometry(
                    data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc
                )
            );

        }

        const folder = gui.addFolder('THREE.TorusBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'tube', 0.1, 10).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 2, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'tubularSegments', 3, 200).step(1).onChange(generateGeometry);
        folder.add(data, 'arc', 0.1, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['TorusBufferGeometry'].generateGeometry = generateGeometry


    },

    TorusGeometry: function (mesh) {

        const data = {
            radius: 10,
            tube: 3,
            radialSegments: 16,
            tubularSegments: 100,
            arc: twoPi
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TorusGeometry(
                    data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc
                )
            );

        }

        const folder = gui.addFolder('THREE.TorusGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'tube', 0.1, 10).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 2, 30).step(1).onChange(generateGeometry);
        folder.add(data, 'tubularSegments', 3, 200).step(1).onChange(generateGeometry);
        folder.add(data, 'arc', 0.1, twoPi).onChange(generateGeometry);

        generateGeometry();
        this['TorusGeometry'].generateGeometry = generateGeometry


    },

    TorusKnotBufferGeometry: function (mesh) {

        const data = {
            radius: 10,
            tube: 3,
            tubularSegments: 64,
            radialSegments: 8,
            p: 2,
            q: 3
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TorusKnotBufferGeometry(
                    data.radius, data.tube, data.tubularSegments, data.radialSegments,
                    data.p, data.q
                )
            );

        }

        const folder = gui.addFolder('THREE.TorusKnotBufferGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'tube', 0.1, 10).onChange(generateGeometry);
        folder.add(data, 'tubularSegments', 3, 300).step(1).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'p', 1, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'q', 1, 20).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TorusKnotBufferGeometry'].generateGeometry = generateGeometry


    },

    TorusKnotGeometry: function (mesh) {

        const data = {
            radius: 10,
            tube: 3,
            tubularSegments: 64,
            radialSegments: 8,
            p: 2,
            q: 3
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TorusKnotGeometry(
                    data.radius, data.tube, data.tubularSegments, data.radialSegments,
                    data.p, data.q
                )
            );

        }

        const folder = gui.addFolder('THREE.TorusKnotGeometry');

        folder.add(data, 'radius', 1, 20).onChange(generateGeometry);
        folder.add(data, 'tube', 0.1, 10).onChange(generateGeometry);
        folder.add(data, 'tubularSegments', 3, 300).step(1).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 3, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'p', 1, 20).step(1).onChange(generateGeometry);
        folder.add(data, 'q', 1, 20).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TorusKnotGeometry'].generateGeometry = generateGeometry


    },

    ParametricBufferGeometry: function (mesh) {

        const data = {
            slices: 25,
            stacks: 25
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new ParametricBufferGeometry(ParametricGeometries.klein, data.slices, data.stacks)
            );

        }

        const folder = gui.addFolder('THREE.ParametricBufferGeometry');

        folder.add(data, 'slices', 1, 100).step(1).onChange(generateGeometry);
        folder.add(data, 'stacks', 1, 100).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ParametricBufferGeometry'].generateGeometry = generateGeometry

    },

    ParametricGeometry: function (mesh) {

        const data = {
            slices: 25,
            stacks: 25
        };

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new ParametricGeometry(ParametricGeometries.klein, data.slices, data.stacks)
            );

        }

        const folder = gui.addFolder('THREE.ParametricGeometry');

        folder.add(data, 'slices', 1, 100).step(1).onChange(generateGeometry);
        folder.add(data, 'stacks', 1, 100).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ParametricGeometry'].generateGeometry = generateGeometry

    },

    TubeGeometry: function (mesh) {

        const data = {
            segments: 20,
            radius: 2,
            radialSegments: 8
        };

        const path = new CustomSinCurve(10);

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TubeGeometry(path, data.segments, data.radius, data.radialSegments, false)
            );

        }

        const folder = gui.addFolder('THREE.TubeGeometry');

        folder.add(data, 'segments', 1, 100).step(1).onChange(generateGeometry);
        folder.add(data, 'radius', 1, 10).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 1, 20).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TubeGeometry'].generateGeometry = generateGeometry

    },

    TubeBufferGeometry: function (mesh) {

        const data = {
            segments: 20,
            radius: 2,
            radialSegments: 8
        };

        const path = new CustomSinCurve(10);

        function generateGeometry() {

            updateGroupGeometry(mesh,
                new TubeBufferGeometry(path, data.segments, data.radius, data.radialSegments, false)
            );

        }

        const folder = gui.addFolder('THREE.TubeBufferGeometry');

        folder.add(data, 'segments', 1, 100).step(1).onChange(generateGeometry);
        folder.add(data, 'radius', 1, 10).onChange(generateGeometry);
        folder.add(data, 'radialSegments', 1, 20).step(1).onChange(generateGeometry);

        generateGeometry();
        this['TubeBufferGeometry'].generateGeometry = generateGeometry

    },

    ShapeGeometry: function (mesh) {

        const data = {
            segments: 12
        };

        function generateGeometry() {

            const geometry = new ShapeGeometry(heartShape, data.segments);
            geometry.center();

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.ShapeGeometry');
        folder.add(data, 'segments', 1, 100).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ShapeGeometry'].generateGeometry = generateGeometry

    },

    ShapeBufferGeometry: function (mesh) {

        const data = {
            segments: 12
        };

        function generateGeometry() {

            const geometry = new ShapeBufferGeometry(heartShape, data.segments);
            geometry.center();

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.ShapeBufferGeometry');
        folder.add(data, 'segments', 1, 100).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ShapeBufferGeometry'].generateGeometry = generateGeometry

    },

    ExtrudeGeometry: function (mesh) {

        const data = {
            steps: 2,
            depth: 16,
            bevelEnabled: true,
            bevelThickness: 1,
            bevelSize: 1,
            bevelOffset: 0,
            bevelSegments: 1
        };

        const length = 12, width = 8;

        const shape = new Shape();
        shape.moveTo(0, 0);
        shape.lineTo(0, width);
        shape.lineTo(length, width);
        shape.lineTo(length, 0);
        shape.lineTo(0, 0);

        function generateGeometry() {

            const geometry = new ExtrudeGeometry(shape, data);
            geometry.center();

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.ExtrudeGeometry');

        folder.add(data, 'steps', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'depth', 1, 20).onChange(generateGeometry);
        folder.add(data, 'bevelThickness', 1, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelSize', 0, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelOffset', -4, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelSegments', 1, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ExtrudeGeometry'].generateGeometry = generateGeometry

    },

    ExtrudeBufferGeometry: function (mesh) {

        const data = {
            steps: 2,
            depth: 16,
            bevelEnabled: true,
            bevelThickness: 1,
            bevelSize: 1,
            bevelOffset: 0,
            bevelSegments: 1
        };

        const length = 12, width = 8;

        const shape = new Shape();
        shape.moveTo(0, 0);
        shape.lineTo(0, width);
        shape.lineTo(length, width);
        shape.lineTo(length, 0);
        shape.lineTo(0, 0);

        function generateGeometry() {

            const geometry = new ExtrudeBufferGeometry(shape, data);
            geometry.center();

            updateGroupGeometry(mesh, geometry);

        }

        const folder = gui.addFolder('THREE.ExtrudeBufferGeometry');

        folder.add(data, 'steps', 1, 10).step(1).onChange(generateGeometry);
        folder.add(data, 'depth', 1, 20).onChange(generateGeometry);
        folder.add(data, 'bevelThickness', 1, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelSize', 0, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelOffset', -4, 5).step(1).onChange(generateGeometry);
        folder.add(data, 'bevelSegments', 1, 5).step(1).onChange(generateGeometry);

        generateGeometry();
        this['ExtrudeBufferGeometry'].generateGeometry = generateGeometry

    },

    WireframeGeometry: function (mesh) {
        isShowGeometry = 'WireframeGeometry'
        const selects = []
        Object.keys(guis).forEach(value => selects.push(value))

        const data = {
            select: 'BoxBufferGeometry',
        };

        function generateGeometry() {
            const geometry = new BoxGeometry(data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments)
            updateGroupGeometry(mesh, geometry);
        }

        const folder = gui.addFolder('THREE.WireframeGeometry');

        folder.add(data, 'select', selects).onChange((value) => {
            console.log(value)
            isShowGeometry = 'WireframeGeometry'
            this[data.select].generateGeometry()
        });

        this[data.select].generateGeometry()
        this['WireframeGeometry'].generateGeometry = generateGeometry
    }

};

function chooseFromHash(mesh) {
    let selectedGeometry = "BoxGeometry";
    Object.keys(guis).forEach((value) => {
        if (guis[value] !== undefined) {
            if (selectedGeometry !== value) guis[value](mesh);
        }
    })
    setTimeout(() => guis[selectedGeometry](mesh))
}

const gui = new GUI();

const scene = new Scene();
scene.background = new Color(0x444444);

const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 50);
camera.position.z = 30;

const canvas = document.getElementById('canvas');
const renderer = new WebGLRenderer({canvas, antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

const orbit = new OrbitControls(camera, canvas);
orbit.enableZoom = false;

const lights = [];
lights[0] = new PointLight(0xffffff, 1, 0);
lights[1] = new PointLight(0xffffff, 1, 0);
lights[2] = new PointLight(0xffffff, 1, 0);

lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);

scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);

const group = new Group();

let geometry = new BufferGeometry();
geometry.setAttribute('position', new Float32BufferAttribute([], 3));

const lineMaterial = new LineBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8});
const meshMaterial = new MeshPhongMaterial({color: 0x156289, emissive: 0x072534, side: DoubleSide, flatShading: true});
group.add(new LineSegments(geometry, lineMaterial));
group.add(new Mesh(geometry, meshMaterial));

chooseFromHash(group);

scene.add(group);

function render() {
    requestAnimationFrame(render);
    group.rotation.x += 0.005;
    group.rotation.y += 0.005;
    renderer.render(scene, camera);
}

window.addEventListener('resize', function () {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);

}, false);

render();
